/* eslint-disable react/display-name */
"use client";

import * as React from "react";

import { type PlateElementProps, type RenderNodeWrapper } from "platejs/react";

import { getDraftCommentKey } from "@platejs/comment";
import { CommentPlugin } from "@platejs/comment/react";
import { SuggestionPlugin } from "@platejs/suggestion/react";
import {
  MessageSquareTextIcon,
  MessagesSquareIcon,
  PencilLineIcon,
} from "lucide-react";
import {
  type AnyPluginConfig,
  type NodeEntry,
  type Path,
  type TCommentText,
  type TElement,
  type TSuggestionText,
  PathApi,
  TextApi,
} from "platejs";
import { useEditorPlugin, useEditorRef, usePluginOption } from "platejs/react";

import { commentPlugin } from "@/components/plate/plugins/comment-kit";
import {
  type TDiscussion,
  discussionPlugin,
} from "@/components/plate/plugins/discussion-kit";
import { suggestionPlugin } from "@/components/plate/plugins/suggestion-kit";
import { Button } from "@/components/plate/ui/button";
import {
  Popover,
  PopoverAnchor,
  PopoverContent,
  PopoverTrigger,
} from "@/components/plate/ui/popover";

import {
  BlockSuggestionCard,
  isResolvedSuggestion,
  useResolveSuggestion,
} from "./block-suggestion";
import { Comment, CommentCreateForm } from "./comment";

export const BlockDiscussion: RenderNodeWrapper&lt;AnyPluginConfig&gt; = (props) =&gt; {
  const { editor, element } = props;

  const commentsApi = editor.getApi(CommentPlugin).comment;
  const blockPath = editor.api.findPath(element);

  // avoid duplicate in table or column
  if (!blockPath || blockPath.length &gt; 1) return;

  const draftCommentNode = commentsApi.node({ at: blockPath, isDraft: true });

  const commentNodes = [...commentsApi.nodes({ at: blockPath })];

  const suggestionNodes = [
    ...editor.getApi(SuggestionPlugin).suggestion.nodes({ at: blockPath }),
  ];

  if (
    commentNodes.length === 0 &&
    suggestionNodes.length === 0 &&
    !draftCommentNode
  ) {
    return;
  }

  return (props) =&gt; (
    &lt;BlockCommentContent
      blockPath={blockPath}
      commentNodes={commentNodes}
      draftCommentNode={draftCommentNode}
      suggestionNodes={suggestionNodes}
      {...props}
    /&gt;
  );
};

const BlockCommentContent = ({
  blockPath,
  children,
  commentNodes,
  draftCommentNode,
  suggestionNodes,
}: PlateElementProps & {
  blockPath: Path;
  commentNodes: NodeEntry&lt;TCommentText&gt;[];
  draftCommentNode: NodeEntry&lt;TCommentText&gt; | undefined;
  suggestionNodes: NodeEntry&lt;TElement | TSuggestionText&gt;[];
}) =&gt; {
  const editor = useEditorRef();

  const resolvedSuggestions = useResolveSuggestion(suggestionNodes, blockPath);
  const resolvedDiscussions = useResolvedDiscussion(commentNodes, blockPath);

  const suggestionsCount = resolvedSuggestions.length;
  const discussionsCount = resolvedDiscussions.length;
  const totalCount = suggestionsCount + discussionsCount;

  const activeSuggestionId = usePluginOption(suggestionPlugin, "activeId");
  const activeSuggestion =
    activeSuggestionId &&
    resolvedSuggestions.find((s) =&gt; s.suggestionId === activeSuggestionId);

  const commentingBlock = usePluginOption(commentPlugin, "commentingBlock");
  const activeCommentId = usePluginOption(commentPlugin, "activeId");
  const isCommenting = activeCommentId === getDraftCommentKey();
  const activeDiscussion =
    activeCommentId &&
    resolvedDiscussions.find((d) =&gt; d.id === activeCommentId);

  const noneActive = !activeSuggestion && !activeDiscussion;

  const sortedMergedData = [
    ...resolvedDiscussions,
    ...resolvedSuggestions,
  ].sort((a, b) =&gt; a.createdAt.getTime() - b.createdAt.getTime());

  const selected =
    resolvedDiscussions.some((d) =&gt; d.id === activeCommentId) ||
    resolvedSuggestions.some((s) =&gt; s.suggestionId === activeSuggestionId);

  const [_open, setOpen] = React.useState(selected);

  // in some cases, we may comment the multiple blocks
  const commentingCurrent =
    !!commentingBlock && PathApi.equals(blockPath, commentingBlock);

  const open =
    _open ||
    selected ||
    (isCommenting && !!draftCommentNode && commentingCurrent);

  const anchorElement = React.useMemo(() =&gt; {
    let activeNode: NodeEntry | undefined;

    if (activeSuggestion) {
      activeNode = suggestionNodes.find(
        ([node]) =&gt;
          TextApi.isText(node) &&
          editor.getApi(SuggestionPlugin).suggestion.nodeId(node) ===
            activeSuggestion.suggestionId,
      );
    }

    if (activeCommentId) {
      if (activeCommentId === getDraftCommentKey()) {
        activeNode = draftCommentNode;
      } else {
        activeNode = commentNodes.find(
          ([node]) =&gt;
            editor.getApi(commentPlugin).comment.nodeId(node) ===
            activeCommentId,
        );
      }
    }

    if (!activeNode) return null;

    return editor.api.toDOMNode(activeNode[0])!;
  }, [
    open,
    activeSuggestion,
    activeCommentId,
    editor.api,
    suggestionNodes,
    draftCommentNode,
    commentNodes,
  ]);

  if (suggestionsCount + resolvedDiscussions.length === 0 && !draftCommentNode)
    return <div>{children}</div>;

  return (
    <div>
      &lt;Popover
        open={open}
        onOpenChange={(_open_) =&gt; {
          if (!_open_ && isCommenting && draftCommentNode) {
            editor.tf.unsetNodes(getDraftCommentKey(), {
              at: [],
              mode: "lowest",
              match: (n) =&gt; n[getDraftCommentKey()],
            });
          }
          setOpen(_open_);
        }}
      &gt;
        <div>{children}</div>
        {anchorElement && (
          &lt;PopoverAnchor
            asChild
            className="w-full"
            virtualRef={{ current: anchorElement }}
          /&gt;
        )}

        &lt;PopoverContent
          className="max-h-[min(50dvh,calc(-24px+var(--radix-popper-available-height)))] w-[380px] min-w-[130px] max-w-[calc(100vw-24px)] overflow-y-auto p-0 data-[state=closed]:opacity-0"
          onCloseAutoFocus={(e) =&gt; e.preventDefault()}
          onOpenAutoFocus={(e) =&gt; e.preventDefault()}
          align="center"
          side="bottom"
        &gt;
          {isCommenting ? (
            &lt;CommentCreateForm className="p-4" focusOnMount /&gt;
          ) : noneActive ? (
            sortedMergedData.map((item, index) =&gt;
              isResolvedSuggestion(item) ? (
                &lt;BlockSuggestionCard
                  key={item.suggestionId}
                  idx={index}
                  isLast={index === sortedMergedData.length - 1}
                  suggestion={item}
                /&gt;
              ) : (
                &lt;BlockComment
                  key={item.id}
                  discussion={item}
                  isLast={index === sortedMergedData.length - 1}
                /&gt;
              ),
            )
          ) : (
            &lt;React.Fragment&gt;
              {activeSuggestion && (
                &lt;BlockSuggestionCard
                  key={activeSuggestion.suggestionId}
                  idx={0}
                  isLast={true}
                  suggestion={activeSuggestion}
                /&gt;
              )}

              {activeDiscussion && (
                &lt;BlockComment discussion={activeDiscussion} isLast={true} /&gt;
              )}
            &lt;/React.Fragment&gt;
          )}
        &lt;/PopoverContent&gt;

        {totalCount &gt; 0 && (
          <div>
            &lt;PopoverTrigger asChild&gt;
              &lt;Button
                variant="ghost"
                className="ml-1 mt-1 flex h-6 gap-1 !px-1.5 py-0 text-muted-foreground/80 hover:text-muted-foreground/80 data-[active=true]:bg-muted"
                data-active={open}
                contentEditable={false}
              &gt;
                {suggestionsCount &gt; 0 && discussionsCount === 0 && (
                  &lt;PencilLineIcon className="size-4 shrink-0" /&gt;
                )}

                {suggestionsCount === 0 && discussionsCount &gt; 0 && (
                  &lt;MessageSquareTextIcon className="size-4 shrink-0" /&gt;
                )}

                {suggestionsCount &gt; 0 && discussionsCount &gt; 0 && (
                  &lt;MessagesSquareIcon className="size-4 shrink-0" /&gt;
                )}

                <span>{totalCount}</span>
              &lt;/Button&gt;
            &lt;/PopoverTrigger&gt;
          </div>
        )}
      &lt;/Popover&gt;
    </div>
  );
};

function BlockComment({
  discussion,
  isLast,
}: {
  discussion: TDiscussion;
  isLast: boolean;
}) {
  const [editingId, setEditingId] = React.useState&lt;string | null&gt;(null);

  return (
    &lt;React.Fragment key={discussion.id}&gt;
      <div>
        {discussion.comments.map((comment, index) =&gt; (
          &lt;Comment
            key={comment.id ?? index}
            comment={comment}
            discussionLength={discussion.comments.length}
            documentContent={discussion?.documentContent}
            editingId={editingId}
            index={index}
            setEditingId={setEditingId}
            showDocumentContent
          /&gt;
        ))}
        &lt;CommentCreateForm discussionId={discussion.id} /&gt;
      </div>

      {!isLast && <div />}
    &lt;/React.Fragment&gt;
  );
}

const useResolvedDiscussion = (
  commentNodes: NodeEntry&lt;TCommentText&gt;[],
  blockPath: Path,
) =&gt; {
  const { api, getOption, setOption } = useEditorPlugin(commentPlugin);

  const discussions = usePluginOption(discussionPlugin, "discussions");

  commentNodes.forEach(([node]) =&gt; {
    const id = api.comment.nodeId(node);
    const map = getOption("uniquePathMap");

    if (!id) return;

    const previousPath = map.get(id);

    // If there are no comment nodes in the corresponding path in the map, then update it.
    if (PathApi.isPath(previousPath)) {
      const nodes = api.comment.node({ id, at: previousPath });

      if (!nodes) {
        setOption("uniquePathMap", new Map(map).set(id, blockPath));
        return;
      }

      return;
    }
    // TODO: fix throw error
    setOption("uniquePathMap", new Map(map).set(id, blockPath));
  });

  const commentsIds = new Set(
    commentNodes.map(([node]) =&gt; api.comment.nodeId(node)).filter(Boolean),
  );

  const resolvedDiscussions = discussions
    .map((d: TDiscussion) =&gt; ({
      ...d,
      createdAt: new Date(d.createdAt),
    }))
    .filter((item: TDiscussion) =&gt; {
      /** If comment cross blocks just show it in the first block */
      const commentsPathMap = getOption("uniquePathMap");
      const firstBlockPath = commentsPathMap.get(item.id);

      if (!firstBlockPath) return false;
      if (!PathApi.equals(firstBlockPath, blockPath)) return false;

      return (
        api.comment.has({ id: item.id }) &&
        commentsIds.has(item.id) &&
        !item.isResolved
      );
    });

  return resolvedDiscussions;
};
